#!/bin/bash
# $Id:$
# Procesor/počítač PDP-8.  Experiment v Bashi.
# Copyright (c) 2010 Radek Hnilica
# All rights reserved.  Všechna práva vyhrazena.



# The processor has following registers:

declare -i reg_pc	   # Program Counter.
declare -i reg_ac	   # Accumulator 12 bit wide
declare -i reg_l	   # Link register, ther 13-th bits of result.
declare -i reg_ir	   # Instruction Register

declare -i mar		   # Memory Address Register
declare -i mdr		   # Memory Data Register

declare state			# run/halt -- computer state

# Initialize the CPU.

cpu_reset() {
    reg_pc=0
    reg_ac=0
    reg_l=0
}

# This function does one step.  This is fetching and processing one
# instruction.

cpu_step() {
    # FETCH
    reg_ir=${memory[$reg_pc]}
    ((reg_pc++)); (( reg_pc > 4095 )) && reg_pc=0 # Advance Program Counter
    
    # Get opcode from Instruction and choose code.
    case $((reg_ir>>9)) in
	0) cpu_do_and;;		# Do AND
	1) cpu_do_tad;;		# Do TAD
	2) cpu_do_isz;;		# Do ISZ
	3) cpu_do_dca;;		# Do DCA
	4) cpu_do_jms;;		# Do JMS
	5) cpu_do_jmp;;		# Do JMP
	6) cpu_do_iot;;		# Do IOT
	7) cpu_do_opr;;		# Do OPR
	*) exit 1;;			# Fatal Error
    esac
}


# Running the computer till it halts by HLT instruction.
cpu_run() {
    local cycles=${1:--1}
    state=run
    while [[ $state == "run" ]]; do
	((cycles--)) || break
	cpu_step
    done
}

# Compute Effective Address of operand from instruction in ir.
cpu_get_ea() {
    declare -ir ia=$(((reg_ir>>8)&1)) # 0-direct, 1-indirect
    declare -ir mp=$(((reg_ir>>7)&1)) # 0-zero page, 1-current page
    declare -i addr=$((reg_ir&0177))
    ((mp)) && ((addr |= reg_pc & 0760))
    ((ia)) && addr=$(memory_read $addr)
    echo $addr
}

# Get operand value computed from instruction in ir.
cpu_get_op() {
    declare -ir addr=$(cpu_get_ea)
    declare -ir op=$(memory_read $addr)
    echo $op
}

cpu_do_and() {
    declare -ir op=$(cpu_get_op)
    (( reg_ac &= $op ))
}

cpu_do_tad() {
    declare -ir op=$(cpu_get_op)
    # Compute Effective Address
    # Get Operand in mdr
    (( reg_ac = (reg_ac + $op) ))
    (( reg_l = (reg_ac>>12) &1 ))
    (( reg_ac &= 07777 ))
}

# Implementation of ISZ instruction.
# ISZ -- Increment and Skip of Zero
# op+1 -> op; if op=0 then pc+1 -> pc
# LINKS:
#  bitsavers/pdf/dec/pdp8/pdp8/F-81_PDP-8_Brochure_Mar65.pdf page 10
#  bitsavers/pdf/dec/pdp8/pdp8/F-85_PDP-8_Users_Handbook_May66.pdf page 210
#  bitsavers/pdf/dec/pdp8/handbooks/MinicomputerHandbook_1976.pdf page 5-3
#  bitsavers/pdf/dec/pdp8/handbooks/Small_Computer_Handbook_1973.pdf page 3-9
#  bitsavers/pdf/dec/pdp5/F-55_PDP5HAndbook_Feb64.pdf page 14
cpu_do_isz() {
    declare -ir ea=$(cpu_get_ea)
    declare -ir op=$(cpu_get_op)
    declare -i w
    (( w = $op + 1))
    (( w &= 07777 ))
    memory_write $w $ea
    if [[ w -eq 0 ]]; then
	((reg_pc = reg_pc + 1))
	((reg_pc &= 07777))
    fi
}


# DCA -- Deposit and Clear AC
# http://www.hnilica.cz/radek/book/electronic/pdp8.isa.dca.html
# AC -> mem[oper]; 0 -> AC
cpu_do_dca() {
    declare -ir ea=$(cpu_get_ea)
    memory_write $reg_ac $ea
    reg_ac=0
} # cpu_do_dca


# JMS -- Jump to Subroutine
# http://www.hnilica.cz/radek/book/electronic/pdp8.isa.jms.html
# PC -> mem[operand]; operand+1 -> PC
cpu_do_jms() {
    declare -ir ea=$(cpu_get_ea)
    memory_write $reg_pc $ea
    (( reg_pc = $ea + 1 ))
}

# JMP -- Jump
cpu_do_jmp() {
    declare -ir ea=$(cpu_get_ea)
    reg_pc=$ea
}

# OPR -- Operate
cpu_do_opr() {
    if ((reg_ir & 0400)); then
	if ((reg_ir & 01)); then
	    echo "Group 3"
	    : opr3
	else
	    cpu_do_opr2
	fi
    else
	cpu_do_opr1
    fi
}

# OPR1 -- Clear, Complement and Rotate
cpu_do_opr1() {
    declare -i link
    # Clear in sequence 1
    # CLA
    if ((reg_ir & 0200)); then
	reg_ac=0
    fi
    # CLL
    if ((reg_ir & 0100)); then
	reg_l=0
    fi
    # Complement in sequence 2
    # CMA
    if ((reg_ir & 0040)); then
	((reg_ac = ~reg_ac & 07777))
    fi
    # CML
    if ((reg_ir & 0020)); then
	((reg_l = ~reg_l & 1))
    fi
    # Increment AC in sequence 3
    # IAC
    if ((reg_ir & 0001)); then
	((reg_ac++))
	if ((reg_ac & 010000)); then
	    ((reg_l = ~reg_l & 01))
	    ((reg_ac &= 07777))
	fi
    fi
    # Rotate In Sequence 4
    if ((reg_ir & 2)); then
	# Double rotate
	if ((reg_ir & 010)); then
	    # RTR
	    cpu_do_rar
	    cpu_do_rar
	fi
	if ((reg_ir & 04)); then
	    # RTL
	    cpu_do_ral
	    cpu_do_ral
	fi
    else # Simple rotate instructions
	if ((reg_ir & 010)); then
	    cpu_do_rar
	fi
	if ((reg_ir & 04)); then
	    cpu_do_ral
	fi
    fi
}

# RAR
cpu_do_rar() {
    link=$reg_l
    ((reg_l = reg_ac & 1))
    ((reg_ac >>= 1))
    ((reg_ac |= (link <<11) )) #
}

# RAL
cpu_do_ral() {
    link=$reg_l
    ((reg_l = reg_ac >> 11))
    ((reg_ac = (reg_ac<<1) | link))	#
    ((reg_ac &= 07777))
}

# Group 2 Microinstructions, mainly skip uOp
cpu_do_opr2() {
    declare -i skip=0
    # Sequence 1: SMA, SPA, SZA, SNA, SNL, SZL, SKP
    if ((reg_ir & 0010)); then # b8 is 1: Skip AND: SPA, SNA, SZL
	skip=1 # SKP is default
	if ((reg_ir & 0100)); then # SPA
	    if ((reg_ac & 04000)); then
		skip=0
	    fi
	fi
	if ((reg_ir & 0040)); then # SNA
	    if ((reg_ac == 0)); then
		skip=0
	    fi
	fi
	if ((reg_ir & 0020)); then # SZL
	    if ((reg_l != 0)); then
		skip=0
	    fi
	fi
    else # b8 is 0: Skip OR.
	# Skip if SMA or SZA or SNL
	if ((reg_ir & 0100)); then # SMA
	    if ((reg_ac & 04000)); then
		skip=1
	    fi
	fi
	if ((reg_ir & 0040)); then # SZA
	    if ((reg_ac == 0)); then
		skip=1
	    fi
	fi
	if ((reg_ir & 0020)); then # SNL
	    if ((reg_l == 1)); then
		skip=1
	    fi
	fi
    fi
    # Now do the skip if $skip condition
    if ((skip)); then
	((reg_pc = (reg_pc+1) & 07777))
    fi

    # Sequence 2: CLA
    if ((reg_ir & 0200)); then # CLA
	reg_ac=0
    fi
    # Sequence 3: OSR
    # Sequence 4: HLT
    if ((reg_ir & 2)); then
	state=halt
    fi
}

cpu_do_opr3() {
    echo $FUNCNAME
}

# IOT -- IO
cpu_do_iot() { :; }

